package prender;

import java.io.IOException;
import java.io.Writer;

import org.apache.log4j.Logger;

class WebBuffer extends  Writer {
	private static final Logger logger = Logger.getLogger("WebBuffer");
	
	private final TopContext topContext;
	TopContext getTopContext() {
		return topContext;
	}

	// only the first WebBuffer has out not null
	private final Writer out;
	// for the first WebBuffer, this field point to itself
	// otherwise, this points to the first WebBuffer
	private final WebBuffer head;
	
	WebBuffer(TopContext topContext, WebBuffer head) {
		this.topContext = topContext;
		this.out = null;
		this.head = head;
	}
	// constructor for the first buffer
	WebBuffer(TopContext topContext, Writer out) {
		this.topContext = topContext;
		this.out = out;
		this.head = this;
		this.pending = false;
	}
	

	// double link
	volatile private WebBuffer prev;
	volatile private WebBuffer next;
	
	/* cached output */
	volatile private StringBuffer sb;
	
	volatile private boolean pending = true;	// when a WebBuffer is created, pending is always true
	/*
	public boolean getPending() {
		return pending;
	}
	*/
	
	/**
	 * Stop pending a buffer.
	 * When a buffer is stop pending, all subsequent buffer that is not pending but blocked by this buffer
	 * will flush as well.
	 * @throws IOException
	 */
	void stopPending() throws IOException {
		
		topContext.linkLock.lock();
		// if this buffer is being blocked, clear the pending bit only
		if (hasBlockerUnsafe()) {
			if (logger.isDebugEnabled()) {
				logger.debug(String.format("stopPending: (this = [%x]), has blocker", this.hashCode()));	
			}
			this.pending = false;
			topContext.linkLock.unlock();
			return ;
		}
		
		this.pending = false;
		
		try {
			// this buffer is completed and there is no blocker, send all buffer to output
			if (logger.isDebugEnabled()) {
				logger.debug(String.format("stopPending: (this = [%x]), no blocker", this.hashCode()));	
			}
				
			for (WebBuffer ptr = this; ptr != null; ptr = ptr.next) {
				synchronized(ptr) {
					if (logger.isDebugEnabled()) {
						logger.debug(String.format("stopPending: (this = [%x]), in loop, ptr = [%s], pending = [%b], sb = [%s]", 
							this.hashCode(), 
							Utility.getObjectSignature(ptr),
							ptr.pending, 
							ptr.sb));
					}
					if (ptr.pending) {
						break;
					}
					if (ptr.sb != null) {
						head.out.write(ptr.sb.toString());
						if (logger.isDebugEnabled()) {
							logger.debug(String.format("stopPending: writing [%s] to output", ptr.sb)); 
						}
						ptr.sb = null;
					}
				}
			}
			// TODO: remove the unnecessary WebBuffer that is not pending and sb is null
		}
		finally {
			topContext.linkLock.unlock();
		}
	}

	
	private boolean hasBlockerUnsafe() {
		for (WebBuffer ptr = prev; ptr != null; ptr = ptr.prev) {
			if (ptr.pending) {
				return true;
			}
		}
		return false;
	}
	
	@Override
	public void write(char[] cbuf, int off, int len) throws IOException {
		if (logger.isDebugEnabled()) {
			logger.debug(String.format("write: enter (this = [%x])", this.hashCode()));
			logger.debug(String.format("write: cbuf = [%s]", new String(cbuf, off, len)));
		}
		
		topContext.linkLock.lock();
		if (hasBlockerUnsafe()) {
			// we have a blocker, only thing we can do is cache the outpout
			synchronized(this) {
				topContext.linkLock.unlock();
				if (sb == null) {
					sb = new StringBuffer();
				}
				sb.append(cbuf, off, len);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("write: cache the content");
				logger.debug("write: leave");
			}
			return ;
		}

		// no blocker, safe to produce the output
		try {
			head.out.write(cbuf, off, len);
			if (logger.isDebugEnabled()) {
				logger.debug("write: write to output");
				logger.debug("write: leave");
			}
			return ;
		} finally {
			topContext.linkLock.unlock();
		}
	}
	

	@Override
	public void flush() throws IOException {
	}

	@Override
	public void close() throws IOException {
	}
	

	/**
	 * Create a new buffer and append it behind the current buffer.
	 * @return
	 */
	WebBuffer insertBuffer() {
		this.topContext.linkLock.lock();
		try {
			WebBuffer wb = new WebBuffer(this.topContext, this.head);
			
			wb.next = this.next;
			wb.prev = this;
			if (this.next != null) {
				this.next.prev = wb;
			}
			this.next = wb;
			
			return wb;
		} finally {
			this.topContext.linkLock.unlock();
		}
	}
	
	// for debug purpose only
	// only called from head
	// call when the TopContext is done or with the linkLock hold
	void dumpBuffersToLogger(String prompt) {
		logger.debug(prompt);
		logger.debug("================================================");
		for (WebBuffer ptr = this; ptr != null; ptr = ptr.next) {
			logger.debug(String.format("buffer = [%s], pending = [%b]", Utility.getObjectSignature(ptr), pending));
		}
		logger.debug("================================================");
		
	}
}
